#include <IdealRadio.h>
#include <omnetpp.h>

#include "IReceptionModel.h"
#include "FreeSpaceModel.h"
#include "FWMath.h"
#include "BasicBattery.h"
#include "BasicModule.h"
#include "NotificationBoard.h"
#include "NotifierConsts.h"
#include "WSNNotifierConsts.h"
#include "NodeStatus.h"
#include "INotifiable.h"
#include "IdealChannelModelAccess.h"
#include "INETDefs.h"


class CC1000IdealRadio : public IdealRadio {

public:
    CC1000IdealRadio();
    virtual ~CC1000IdealRadio();
    virtual void initialize(int stage);
    virtual void handleMessage(cMessage *msg);
    virtual void stopRadio();
    virtual void setRadioState(RadioState::State newState);
    virtual void registerBattery();
    double calculateDistance(double pSend, double pRec, double carrierFrequency);
    virtual void receiveChangeNotification(int category, const cObject *details);

protected:
    bool isUp;
    NotificationBoard *wnb;
    RadioState _rs;
    double carrierFrequency;
    double sensitivity;
    double transmitterPower;
};

Define_Module(CC1000IdealRadio);

CC1000IdealRadio :: CC1000IdealRadio() : wnb(NULL), _rs(this->getId()) {
    isUp = true;
    IdealRadio();
}

CC1000IdealRadio :: ~CC1000IdealRadio() {

}


void CC1000IdealRadio :: initialize(int stage) {

    // IdealRadio::initialize(stage);
    EV << "Extended from Ideal radio: CC1000IdealRadio";
    IdealRadio::IdealChannelModelAccess::initialize(stage);
    EV << "Initializing IdealRadio, stage=" << stage << endl;

    if (stage == 0)
    {
        wnb = NotificationBoardAccess().get();
        wnb->subscribe(this, BATTERY_RESIDUAL_EMPTY);
        inTransmit = false;
        concurrentReceives = 0;

        upperLayerInGateId = gate("upperLayerIn")->getId();
        upperLayerOutGateId = gate("upperLayerOut")->getId();
        radioInGateId = gate("radioIn")->getId();

        gate(radioInGateId)->setDeliverOnReceptionStart(true);

        // read parameters
        sensitivity = FWMath::dBm2mW(par("sensitivity"));
        carrierFrequency = par("carrierFrequency");
        transmitterPower = par("transmitterPower");
        //if (transmitterPower > (double) (getChannelControlPar("pMax")))
        //            error("transmitterPower cannot be bigger than pMax in ChannelControl!");

        transmissionRange = calculateDistance(transmitterPower, sensitivity, carrierFrequency);
        EV << "Max distance: " << transmissionRange;
        bitrate = par("bitrate").doubleValue();
        drawCoverage = true;

        rs = RadioState::IDLE;
        _rs.setState(rs);
        WATCH(rs);

        // signals
        radioStateSignal = registerSignal("radioState");
    }
    else if (stage == 1)
    {
        registerBattery();

        bool isOperational;
        NodeStatus *nodeStatus = dynamic_cast<NodeStatus *>(findContainingNode(this)->getSubmodule("status"));
        isOperational = (!nodeStatus) || nodeStatus->getState() == NodeStatus::UP;
        rs = isOperational ? RadioState::IDLE : RadioState::OFF;
        _rs.setState(rs);
        if (isOperational) {

            wnb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &_rs);
        }
    }
    else if (stage == 2)
    {
        emit(radioStateSignal, rs);

        // draw the interference distance
        if (ev.isGUI() && drawCoverage)
            updateDisplayString();
    }

}

void CC1000IdealRadio :: handleMessage(cMessage *msg) {

    if (isUp == true) {

        IdealRadio::handleMessage(msg);
    }
    else {

        delete msg;
    }
}

void CC1000IdealRadio :: stopRadio() {

    if (isUp == true) {

        EV << "Battery capacity is empty, turn down radio" << endl;
        isUp = false;
        rs = RadioState::OFF;
        _rs.setState(rs);
        transmissionRange = 0;
        for (RecvBuff::iterator it = recvBuff.begin(); it!=recvBuff.end(); ++it)
        {
            cMessage *endRxTimer = *it;
            cancelAndDelete(endRxTimer);
        }
        recvBuff.clear();

        if (ev.isGUI() && drawCoverage)
            updateDisplayString();
    }
}

double CC1000IdealRadio:: calculateDistance(double pSend, double pRec, double carrierFrequency)
{
    double pathLossAlpha = par("pathLossAlpha");
    double Gt = pow(10, par("TransmissionAntennaGainIndB").doubleValue()/10);
    double Gr = pow(10, par("ReceiveAntennaGainIndB").doubleValue()/10);
    double L = pow(10, par("SystemLossFactor").doubleValue()/10);
  /** @brief
     * Friis free space equation:
     *
     *       Pt * Gt * Gr * (lambda^2)
     *   P = --------------------------
     *       (4 * pi)^2 * d^alpha * L
     */

  if (pSend == pRec)
    return 0.0;

  double lambda = SPEED_OF_LIGHT / carrierFrequency;
 /** @return returns a power value */
  // Antennengewinn eines lambda/2-Dipols ist etwa 2,15 dBi
  // Pt is in milli watt

  double aux  = (pSend * lambda * lambda * Gt * Gr / (16.0 *M_PI * M_PI * pRec * L));
  return pow(aux, 1.0 / pathLossAlpha);
}

void CC1000IdealRadio :: setRadioState(RadioState::State newState) {

    IdealRadio::setRadioState(newState);
    if (_rs.getState() != newState) {
        _rs.setState(newState);
        wnb->fireChangeNotification(NF_RADIOSTATE_CHANGED, &_rs);
    }
}

void CC1000IdealRadio :: registerBattery(){

    BasicBattery *bat = BatteryAccess().getIfExists();
    if (bat)
    {
        //int id,double mUsageRadioIdle,double mUsageRadioRecv,double mUsageRadioSend,double mUsageRadioSleep)=0;
        // read parameters
        double mUsageRadioIdle = par("usage_radio_idle");
        double mUsageRadioRecv = par("usage_radio_recv");
        double mUsageRadioSleep = par("usage_radio_sleep");
        double mUsageRadioSend = par("usage_radio_send");
        if (mUsageRadioIdle<0 || mUsageRadioRecv<0 || mUsageRadioSleep<0 || mUsageRadioSend < 0)
            return;
        bat->registerWirelessDevice(_rs.getRadioId(), mUsageRadioIdle, mUsageRadioRecv, mUsageRadioSend, mUsageRadioSleep);
    }
}

void CC1000IdealRadio :: receiveChangeNotification(int category, const cObject *details) {

    if (category == BATTERY_RESIDUAL_EMPTY) {

        stopRadio();
    }
    else {

        IdealChannelModelAccess :: receiveChangeNotification(category, details);
    }
}
